/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.kernel;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * This class implements a reflection-based dispatcher.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
final class ReflectionDispatcher implements Dispatcher {

    /** Size of names and implementations arrays. */
    private int size;

    /** Array of primitive names. */
    private String[] names;

    /** Array of primitive implementations. */
    private Method[] implementations;

    /**
     * Constructs a dispatcher from primitive names and implementations.
     * @param names primitive names - should not be <tt>null</tt><br/>
     *              should have the same length as <tt>impl</tt>
     * @param impls primitive implementations - should not be <tt>null</tt><br/>
     *              should have the same length as <tt>names</tt>
     */
    ReflectionDispatcher(final String[] names, final Method[] impls) {
        assert names != null : "null names";
        assert impls != null : "null impls";
        assert names.length == impls.length : "names and impls should have the same length";
        this.size = names.length;
        this.names = Arrays.copyOf(names, names.length);
        this.implementations = Arrays.copyOf(impls, impls.length);
    } // end constructor(String[], Method[])

    /**
     * {@inheritDoc}
     */
    @Override
    public String[] getNames() {
        return this.names;
    } // end method 'getNames()'

    /**
     * {@inheritDoc}
     */
    @Override
    public Method[] getMethods() {
        return this.implementations;
    } // end method 'getMethods()'

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName(final int prim) {
        if ((prim >= 0) && (prim < this.size)) {
            return this.names[prim];
        } else {
            return null;
        } // end if/else
    } // end method 'getName(int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public Method getMethod(final int prim) {
        if ((prim >= 0) && (prim < this.size)) {
            return this.implementations[prim];
        } else {
            return null;
        } // end if/else
    } // end method 'getMethod(int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        return this.size;
    } // end method 'size()'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value invoke(final int prim,
                        final CodeRunner ctxt,
                        final Value[] params)
        throws Fatal.Exception, Fail.Exception, FalseExit {
        assert ctxt != null : "null ctxt";
        assert params != null : "null params";
        if ((prim >= 0) && (prim < this.size)) {
            final int len = params.length + 1;
            final Object[] p = new Object[len];
            p[0] = ctxt;
            System.arraycopy(params, 0, p, 1, len - 1);
            try {
                return (Value) this.implementations[prim].invoke(null, p);
            } catch (final IllegalAccessException iae) {
                Fatal.raise("illegal access to primitive: " + getName(prim));
                return null; // never reached
            } catch (final Throwable t) {
                final Throwable cause = t.getCause();
                if (cause instanceof Fail.Exception) {
                    throw (Fail.Exception) cause;
                } else if (cause instanceof Fatal.Exception) {
                    throw (Fatal.Exception) cause;
                } else if (cause instanceof FalseExit) {
                    throw (FalseExit) cause;
                } else {
                    final StringWriter sw = new StringWriter();
                    sw.append("primitive error: ");
                    sw.append(getName(prim));
                    sw.append('\n');
                    if (cause != null) {
                        cause.printStackTrace(new PrintWriter(sw));
                    } // end if
                    Fatal.raise(sw.toString());
                    return null; // never reached
                } // end if/elsif/else
            } // end try/catch
        } else {
            Fatal.raise("undefined primitive: " + prim);
            return null; // never reached
        } // end if/else
    } // end method 'invoke(int, CodeRunner, Value[])'

    /**
     * {@inheritDoc}
     */
    @Override
    public int addPrimitive(final String name, final Method impl) {
        assert name != null : "null name";
        assert impl != null : "null impl";
        final int oldSize = this.size++;
        final Method[] impls = new Method[this.size];
        System.arraycopy(this.implementations, 0, impls, 0, oldSize);
        impls[oldSize] = impl;
        this.implementations = impls;
        final String[] names = new String[this.size];
        System.arraycopy(this.names, 0, names, 0, oldSize);
        names[oldSize] = name;
        this.names = names;
        return oldSize;
    } // end method 'addPrimitive(String, Method)'

} // end class 'ReflectionDispatcher'
